package misc

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"
	"unicode/utf8"
)

const readerBufferSize = 2048

var Upgrader = websocket.Upgrader{
	// cross origin domain
	CheckOrigin: func(ctx *http.Request) bool { //这个是解决跨域问题
		return true
	},
	Subprotocols: []string{"Sec-WebSocket-Protocol"},
}

var upgrader = websocket.Upgrader{
	// cross origin domain
	CheckOrigin: func(ctx *http.Request) bool { //这个是解决跨域问题
		return true
	},
	Subprotocols: []string{"Sec-WebSocket-Protocol"},
}

func InitWsRouter(routerGroup gin.RouterGroup) {
	routerGroup.Any("/echo", EchoWSHandler)

	// curl -X POST -H "Content-Type: application/json" -H "endpoint_version: v1.43" -d '{"AttachStderr":true,"AttachStdin":true,"AttachStdout":true,"Cmd":["/bin/bash"],"DetachKeys":"ctrl-z","Env":[],"Tty":true,"User":"root"}' http://localhost:39781/docker-api/containers/a2cbd5ead1cf/exec
	//{"Id":"ced818560eee77d3437f8dac7c0eea3f57f94056e036697a7bb5344ba53866c5"}

	// curl -X POST --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Detach":false,"Tty":true}' http://v1.43/exec/d5583ee3e8ab17342654eca3f6356e37be5d8e3ffc4a58cb98c5bb3b1fb3ea52/start
	// http://127.0.0.1:39781/blockchainsaio/docker-api-ws/exec?id=8dbb05781ba3e1408b857d18581f1cdc596c6d21b62b77e908467d8ff345ff61
	routerGroup.Any("/exec", ExecWSHandler)
}

func EchoWSHandler(ctx *gin.Context) {
	echo(ctx.Writer, ctx.Request)
}

func ExecWSHandler(ctx *gin.Context) {
	execId := ctx.Query("id")

	if len(execId) == 0 {
		ctx.JSON(400, "没有设置需要执行的ID")
		return
	}

	//ri, err := getRequestInfo4WS("/docker-api-ws", ctx)
	//ri, err := GetRequestInfo(ctx.Param("path"), ctx)
	//if err != nil {
	//	return
	//}

	ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
	if err != nil {
		return
	}

	defer ws.Close()
	ctx.Request.Header.Del("Origin")
	//hijackExecStartOperation(ws, ri.Host, execId, ri.Version)
	hijackExecStartOperation(ws, "unix:///var/run/docker.sock", execId, "1.43")

	if err != nil {
		if _, ok := err.(websocket.HandshakeError); ok {
			log.Println(err)
		}
		return
	}

	//ctx.Request.Response.Header.Set("Sec-WebSocket-Protocol", ri.Version)
	//ctx.Request.Response.Header.Set("Sec-WebSocket-Protocol", "1.43")

	return
}

func hijackExecStartOperation(websocketConn *websocket.Conn, endpoint string, execID string, version string) error {
	if strings.Index(endpoint, "unix") == 0 || strings.Index(endpoint, "UNIX") == 0 {
		endpoint = "unix:///var/run/docker.sock"
		//endpoint = "/var/run/docker.sock"
	}

	dial, err := InitClientDial(endpoint)

	if err != nil {
		return err
	}

	if tcpConn, ok := dial.(*net.TCPConn); ok {
		tcpConn.SetKeepAlive(true)
		tcpConn.SetKeepAlivePeriod(30 * time.Second)
	}

	httpConn := httputil.NewClientConn(dial, nil)
	defer httpConn.Close()

	execStartRequest, err := createExecStartRequest(execID, version)
	if err != nil {
		return err
	}

	err = HijackClientRequest(websocketConn, httpConn, execStartRequest)
	if err != nil {
		return err
	}

	return nil
}

func getRequestInfo4WS(context string, ctx *gin.Context) (rtn RequestInfo, err error) {

	path := string(ctx.FullPath())

	uris := strings.Split(path, context+"/")

	if len(uris) <= 1 {
		err = errors.New(path + " is not good")
		return
	}

	arr := strings.Split(string(ctx.Request.Header.Get("Sec-WebSocket-Protocol")), ",")

	if len(arr) != 3 {
		err = errors.New(string(ctx.Request.Header.Get("Sec-WebSocket-Protocol")) + " is not good")
		return
	}

	if len(strings.TrimSpace(arr[0])) == 0 {
		err = errors.New("not set endpoint in request header, you must set endpoint in your request header")
		return
	}

	version := strings.TrimSpace(arr[2])

	if len(version) == 0 {
		version = "v1.32"
	}

	if strings.Index(version, "v1.") != 0 {
		err = errors.New("endpoint_version is not right, please check it in request header")
		return
	}

	rtn.Host = strings.TrimSpace(arr[0]) + ":" + strings.TrimSpace(arr[1])
	rtn.Api = uris[1]
	rtn.Version = version
	rtn.Path = "/" + version + "/" + uris[1]

	return
}

func createExecStartRequest(execID string, version string) (*http.Request, error) {
	params := make(map[string]any)
	params["Tty"] = true
	params["Detach"] = false

	encodedBody := bytes.NewBuffer(nil)
	err := json.NewEncoder(encodedBody).Encode(params)
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", "http://"+version+"/exec/"+execID+"/start", encodedBody)
	if err != nil {
		return nil, err
	}

	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Connection", "Upgrade")
	request.Header.Set("Upgrade", "tcp")

	return request, nil
}

// InitClientDial /var/run/docker.sock
func InitClientDial(endpoint string) (net.Conn, error) {
	if strings.Index(strings.ToLower(endpoint), "unix") == 0 {

		unix := strings.ReplaceAll(endpoint, "unix://", "")
		unix = strings.ReplaceAll(unix, "UNIX://", "")
		conn, err := net.Dial("unix", unix)
		if err != nil {
			log.Printf("Unix Dail error: %v", err)
			return nil, err
		} else {
			return conn, nil
		}
	} else {
		ip := strings.ReplaceAll(endpoint, "tcp://", "")
		ip = strings.ReplaceAll(ip, "TCP://", "")
		return net.Dial("tcp", ip)
	}
}

func HijackClientRequest(websocketConn *websocket.Conn, httpConn *httputil.ClientConn, request *http.Request) error {
	// Server hijacks the connection, error 'connection closed' expected
	resp, err := httpConn.Do(request)
	if err != httputil.ErrPersistEOF {
		if err != nil {
			return err
		}
		if resp.StatusCode != http.StatusSwitchingProtocols {
			resp.Body.Close()
			return fmt.Errorf("unable to upgrade to tcp, received %d", resp.StatusCode)
		}
	}
	//if err != nil && err != httputil.ErrPersistEOF {
	//	if err != nil {
	//		return err
	//	}
	//	if resp.StatusCode != http.StatusSwitchingProtocols {
	//		resp.Body.Close()
	//		return fmt.Errorf("unable to upgrade to tcp, received %d", resp.StatusCode)
	//	}
	//}

	tcpConn, brw := httpConn.Hijack()
	defer tcpConn.Close()

	errorChan := make(chan error, 1)
	go streamFromReaderToWebsocket(websocketConn, brw, errorChan)
	go streamFromWebsocketToWriter(websocketConn, tcpConn, errorChan)

	err = <-errorChan
	if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
		return err
	}

	return nil
}

func streamFromReaderToWebsocket(websocketConn *websocket.Conn, reader io.Reader, errorChan chan error) {
	for {
		out := make([]byte, readerBufferSize)
		_, err := reader.Read(out)
		if err != nil {
			errorChan <- err
			break
		}

		processedOutput := validString(string(out[:]))

		log.Println(processedOutput)

		err = websocketConn.WriteMessage(websocket.TextMessage, []byte(processedOutput))
		if err != nil {
			errorChan <- err
			break
		}
	}
}

func streamFromWebsocketToWriter(websocketConn *websocket.Conn, writer io.Writer, errorChan chan error) {
	for {
		_, in, err := websocketConn.ReadMessage()
		if err != nil {
			errorChan <- err
			break
		}

		log.Println(string(in))

		_, err = writer.Write(in)
		if err != nil {
			errorChan <- err
			break
		}
	}
}

func validString(s string) string {
	if !utf8.ValidString(s) {
		v := make([]rune, 0, len(s))
		for i, r := range s {
			if r == utf8.RuneError {
				_, size := utf8.DecodeRuneInString(s[i:])
				if size == 1 {
					continue
				}
			}
			v = append(v, r)
		}
		s = string(v)
	}
	return s
}

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %s", message)
		err = c.WriteMessage(mt, message)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}
